/****************************************************************************
 *
 * Copyright 2016 Samsung Electronics All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 ****************************************************************************/
/*
 * Wslay - The WebSocket Library
 *
 * Copyright (c) 2011, 2012 Tatsuhiro Tsujikawa
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/* Start of utf8 dfa */
/* Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de>
 * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
 *
 * Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
#include "wslay_event.h"

#include <string.h>
#include <assert.h>
#include <stdio.h>

#include "wslay_queue.h"
#include "wslay_frame.h"
#include "wslay_net.h"

#include <apps/netutils/websocket.h>
#define UTF8_ACCEPT 0
#define UTF8_REJECT 12

static const uint8_t utf8d[] = {
	/*
	 * The first part of the table maps bytes to character classes that
	 * to reduce the size of the transition table and create bitmasks.
	 */
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
	7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
	8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
	10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,

	/*
	 * The second part is a transition table that maps a combination
	 * of a state of the automaton and a character class to a state.
	 */
	0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
	12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12,
	12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12,
	12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12,
	12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
};

static uint32_t decode(uint32_t *state, uint32_t *codep, uint32_t byte)
{
	uint32_t type = utf8d[byte];

	*codep = (*state != UTF8_ACCEPT) ? (byte & 0x3fu) | (*codep << 6) : (0xff >> type) & (byte);

	*state = utf8d[256 + *state + type];
	return *state;
}

/* End of utf8 dfa */

static ssize_t wslay_event_frame_recv_callback(uint8_t *buf, size_t len, int flags, void *user_data)
{
	struct wslay_event_frame_user_data *e = (struct wslay_event_frame_user_data *)user_data;
	return e->ctx->callbacks.recv_callback(e->ctx, buf, len, flags, e->user_data);
}

static ssize_t wslay_event_frame_send_callback(const uint8_t *data, size_t len, int flags, void *user_data)
{
	struct wslay_event_frame_user_data *e = (struct wslay_event_frame_user_data *)user_data;
	return e->ctx->callbacks.send_callback(e->ctx, data, len, flags, e->user_data);
}

static int wslay_event_frame_genmask_callback(uint8_t *buf, size_t len, void *user_data)
{
	struct wslay_event_frame_user_data *e = (struct wslay_event_frame_user_data *)user_data;
	return e->ctx->callbacks.genmask_callback(e->ctx, buf, len, e->user_data);
}

static int wslay_event_byte_chunk_init(struct wslay_event_byte_chunk **chunk, size_t len)
{
	*chunk = (struct wslay_event_byte_chunk *)malloc(sizeof(struct wslay_event_byte_chunk));
	if (*chunk == NULL) {
		return WSLAY_ERR_NOMEM;
	}
	memset(*chunk, 0, sizeof(struct wslay_event_byte_chunk));
	if (len) {
		(*chunk)->data = (uint8_t *)malloc(len);
		if ((*chunk)->data == NULL) {
			free(*chunk);
			return WSLAY_ERR_NOMEM;
		}
		(*chunk)->data_length = len;
	}
	return 0;
}

static void wslay_event_byte_chunk_free(struct wslay_event_byte_chunk *c)
{
	if (!c) {
		return;
	}
	free(c->data);
	free(c);
}

static void wslay_event_byte_chunk_copy(struct wslay_event_byte_chunk *c, size_t off, const uint8_t *data, size_t data_length)
{
	memcpy(c->data + off, data, data_length);
}

static void wslay_event_imsg_set(struct wslay_event_imsg *m, uint8_t fin, uint8_t rsv, uint8_t opcode)
{
	m->fin = fin;
	m->rsv = rsv;
	m->opcode = opcode;
	m->msg_length = 0;
}

static void wslay_event_imsg_chunks_free(struct wslay_event_imsg *m)
{
	if (!m->chunks) {
		return;
	}
	while (!wslay_queue_empty(m->chunks)) {
		wslay_event_byte_chunk_free(wslay_queue_top(m->chunks));
		wslay_queue_pop(m->chunks);
	}
}

static void wslay_event_imsg_reset(struct wslay_event_imsg *m)
{
	m->opcode = 0xffu;
	m->utf8state = UTF8_ACCEPT;
	wslay_event_imsg_chunks_free(m);
}

static int wslay_event_imsg_append_chunk(struct wslay_event_imsg *m, size_t len)
{
	if (len == 0) {
		return 0;
	} else {
		int r;
		struct wslay_event_byte_chunk *chunk;
		if ((r = wslay_event_byte_chunk_init(&chunk, len)) != 0) {
			return r;
		}
		if ((r = wslay_queue_push(m->chunks, chunk)) != 0) {
			return r;
		}
		m->msg_length += len;
		return 0;
	}
}

static int wslay_event_omsg_non_fragmented_init(struct wslay_event_omsg **m, uint8_t opcode, uint8_t rsv, const uint8_t *msg, size_t msg_length)
{
	*m = (struct wslay_event_omsg *)malloc(sizeof(struct wslay_event_omsg));
	if (!*m) {
		return WSLAY_ERR_NOMEM;
	}
	memset(*m, 0, sizeof(struct wslay_event_omsg));
	(*m)->fin = 1;
	(*m)->opcode = opcode;
	(*m)->rsv = rsv;
	(*m)->type = WSLAY_NON_FRAGMENTED;
	if (msg_length) {
		(*m)->data = (uint8_t *)malloc(msg_length);
		if (!(*m)->data) {
			free(*m);
			return WSLAY_ERR_NOMEM;
		}
		memcpy((*m)->data, msg, msg_length);
		(*m)->data_length = msg_length;
	}
	return 0;
}

static int wslay_event_omsg_fragmented_init(struct wslay_event_omsg **m, uint8_t opcode, uint8_t rsv, const union wslay_event_msg_source source, wslay_event_fragmented_msg_callback read_callback)
{
	*m = (struct wslay_event_omsg *)malloc(sizeof(struct wslay_event_omsg));
	if (!*m) {
		return WSLAY_ERR_NOMEM;
	}
	memset(*m, 0, sizeof(struct wslay_event_omsg));
	(*m)->opcode = opcode;
	(*m)->rsv = rsv;
	(*m)->type = WSLAY_FRAGMENTED;
	(*m)->source = source;
	(*m)->read_callback = read_callback;
	return 0;
}

static void wslay_event_omsg_free(struct wslay_event_omsg *m)
{
	if (!m) {
		return;
	}
	free(m->data);
	free(m);
}

static uint8_t *wslay_event_flatten_queue(struct wslay_queue *queue, size_t len)
{
	if (len == 0) {
		return NULL;
	} else {
		size_t off = 0;
		uint8_t *buf = (uint8_t *)malloc(len);
		if (!buf) {
			return NULL;
		}
		while (!wslay_queue_empty(queue)) {
			struct wslay_event_byte_chunk *chunk = wslay_queue_top(queue);
			memcpy(buf + off, chunk->data, chunk->data_length);
			off += chunk->data_length;
			wslay_event_byte_chunk_free(chunk);
			wslay_queue_pop(queue);
			assert(off <= len);
		}
		assert(len == off);
		return buf;
	}
}

static int wslay_event_is_msg_queueable(wslay_event_context_ptr ctx)
{
	return ctx->write_enabled && (ctx->close_status & WSLAY_CLOSE_QUEUED) == 0;
}

int wslay_event_queue_close(wslay_event_context_ptr ctx, uint16_t status_code, const uint8_t *reason, size_t reason_length)
{
	if (!wslay_event_is_msg_queueable(ctx)) {
		return WSLAY_ERR_NO_MORE_MSG;
	} else if (reason_length > 123) {
		return WSLAY_ERR_INVALID_ARGUMENT;
	} else {
		uint8_t msg[128];
		size_t msg_length;
		struct wslay_event_msg arg;
		uint16_t ncode;
		int r;
		if (status_code == 0) {
			msg_length = 0;
		} else {
			ncode = htons(status_code);
			memcpy(msg, &ncode, 2);
			memcpy(msg + 2, reason, reason_length);
			msg_length = reason_length + 2;
		}
		arg.opcode = WSLAY_CONNECTION_CLOSE;
		arg.msg = msg;
		arg.msg_length = msg_length;
		r = wslay_event_queue_msg(ctx, &arg);
		if (r == 0) {
			ctx->close_status |= WSLAY_CLOSE_QUEUED;
		}
		return r;
	}
}

static int wslay_event_queue_close_wrapper(wslay_event_context_ptr ctx, uint16_t status_code, const uint8_t *reason, size_t reason_length)
{
	int r;
	ctx->read_enabled = 0;
	if ((r = wslay_event_queue_close(ctx, status_code, reason, reason_length)) && r != WSLAY_ERR_NO_MORE_MSG) {
		return r;
	}
	return 0;
}

static int wslay_event_verify_rsv_bits(wslay_event_context_ptr ctx, uint8_t rsv)
{
	return ((rsv & ~ctx->allowed_rsv_bits) == 0);
}

int wslay_event_queue_msg(wslay_event_context_ptr ctx, const struct wslay_event_msg *arg)
{
	return wslay_event_queue_msg_ex(ctx, arg, WSLAY_RSV_NONE);
}

int wslay_event_queue_msg_ex(wslay_event_context_ptr ctx, const struct wslay_event_msg *arg, uint8_t rsv)
{
	int r;
	struct wslay_event_omsg *omsg;
	if (!wslay_event_is_msg_queueable(ctx)) {
		return WSLAY_ERR_NO_MORE_MSG;
	}
	/* RSV1 is not allowed for control frames */
	if ((wslay_is_ctrl_frame(arg->opcode) && (arg->msg_length > 125 || wslay_get_rsv1(rsv)))
		|| !wslay_event_verify_rsv_bits(ctx, rsv)) {
		return WSLAY_ERR_INVALID_ARGUMENT;
	}
	if ((r = wslay_event_omsg_non_fragmented_init(&omsg, arg->opcode, rsv, arg->msg, arg->msg_length)) != 0) {
		return r;
	}
	if (wslay_is_ctrl_frame(arg->opcode)) {
		if ((r = wslay_queue_push(ctx->send_ctrl_queue, omsg)) != 0) {
			return r;
		}
	} else {
		if ((r = wslay_queue_push(ctx->send_queue, omsg)) != 0) {
			return r;
		}
	}
	++ctx->queued_msg_count;
	ctx->queued_msg_length += arg->msg_length;
	return 0;
}

int wslay_event_queue_fragmented_msg(wslay_event_context_ptr ctx, const struct wslay_event_fragmented_msg *arg)
{
	return wslay_event_queue_fragmented_msg_ex(ctx, arg, WSLAY_RSV_NONE);
}

int wslay_event_queue_fragmented_msg_ex(wslay_event_context_ptr ctx, const struct wslay_event_fragmented_msg *arg, uint8_t rsv)
{
	int r;
	struct wslay_event_omsg *omsg;
	if (!wslay_event_is_msg_queueable(ctx)) {
		return WSLAY_ERR_NO_MORE_MSG;
	}
	if (wslay_is_ctrl_frame(arg->opcode) || !wslay_event_verify_rsv_bits(ctx, rsv)) {
		return WSLAY_ERR_INVALID_ARGUMENT;
	}
	if ((r = wslay_event_omsg_fragmented_init(&omsg, arg->opcode, rsv, arg->source, arg->read_callback)) != 0) {
		return r;
	}
	if ((r = wslay_queue_push(ctx->send_queue, omsg)) != 0) {
		return r;
	}
	++ctx->queued_msg_count;
	return 0;
}

void wslay_event_config_set_callbacks(wslay_event_context_ptr ctx, const struct wslay_event_callbacks *callbacks)
{
	ctx->callbacks = *callbacks;
}

static int wslay_event_context_init(wslay_event_context_ptr *ctx, const struct wslay_event_callbacks *callbacks, void *user_data)
{
	int i, r;
	struct wslay_frame_callbacks frame_callbacks = {
		wslay_event_frame_send_callback,
		wslay_event_frame_recv_callback,
		wslay_event_frame_genmask_callback
	};
	*ctx = (wslay_event_context_ptr)malloc(sizeof(struct wslay_event_context));
	if (!*ctx) {
		return WSLAY_ERR_NOMEM;
	}
	memset(*ctx, 0, sizeof(struct wslay_event_context));
	wslay_event_config_set_callbacks(*ctx, callbacks);
	(*ctx)->user_data = user_data;
	(*ctx)->frame_user_data.ctx = *ctx;
	(*ctx)->frame_user_data.user_data = user_data;
	if ((r = wslay_frame_context_init(&(*ctx)->frame_ctx, &frame_callbacks, &(*ctx)->frame_user_data)) != 0) {
		wslay_event_context_free(*ctx);
		return r;
	}
	(*ctx)->read_enabled = (*ctx)->write_enabled = 1;
	(*ctx)->send_queue = wslay_queue_new();
	if (!(*ctx)->send_queue) {
		wslay_event_context_free(*ctx);
		return WSLAY_ERR_NOMEM;
	}
	(*ctx)->send_ctrl_queue = wslay_queue_new();
	if (!(*ctx)->send_ctrl_queue) {
		wslay_event_context_free(*ctx);
		return WSLAY_ERR_NOMEM;
	}
	(*ctx)->queued_msg_count = 0;
	(*ctx)->queued_msg_length = 0;
	for (i = 0; i < 2; ++i) {
		wslay_event_imsg_reset(&(*ctx)->imsgs[i]);
		(*ctx)->imsgs[i].chunks = wslay_queue_new();
		if (!(*ctx)->imsgs[i].chunks) {
			wslay_event_context_free(*ctx);
			return WSLAY_ERR_NOMEM;
		}
	}
	(*ctx)->imsg = &(*ctx)->imsgs[0];
	(*ctx)->obufmark = (*ctx)->obuflimit = (*ctx)->obuf;
	(*ctx)->status_code_sent = WSLAY_CODE_ABNORMAL_CLOSURE;
	(*ctx)->status_code_recv = WSLAY_CODE_ABNORMAL_CLOSURE;
	(*ctx)->max_recv_msg_length = (1u << 31) - 1;
	return 0;
}

int wslay_event_context_server_init(wslay_event_context_ptr *ctx, const struct wslay_event_callbacks *callbacks, void *user_data)
{
	int r;
	if ((r = wslay_event_context_init(ctx, callbacks, user_data)) != 0) {
		return r;
	}
	(*ctx)->server = 1;
	return 0;
}

int wslay_event_context_client_init(wslay_event_context_ptr *ctx, const struct wslay_event_callbacks *callbacks, void *user_data)
{
	int r;
	if ((r = wslay_event_context_init(ctx, callbacks, user_data)) != 0) {
		return r;
	}
	(*ctx)->server = 0;
	return 0;
}

void wslay_event_context_free(wslay_event_context_ptr ctx)
{
	int i;
	if (!ctx) {
		return;
	}
	if (ctx->user_data) {
		free(ctx->user_data);
	}
	for (i = 0; i < 2; ++i) {
		wslay_event_imsg_chunks_free(&ctx->imsgs[i]);
		wslay_queue_free(ctx->imsgs[i].chunks);
	}
	if (ctx->send_queue) {
		while (!wslay_queue_empty(ctx->send_queue)) {
			wslay_event_omsg_free(wslay_queue_top(ctx->send_queue));
			wslay_queue_pop(ctx->send_queue);
		}
		wslay_queue_free(ctx->send_queue);
	}
	if (ctx->send_ctrl_queue) {
		while (!wslay_queue_empty(ctx->send_ctrl_queue)) {
			wslay_event_omsg_free(wslay_queue_top(ctx->send_ctrl_queue));
			wslay_queue_pop(ctx->send_ctrl_queue);
		}
		wslay_queue_free(ctx->send_ctrl_queue);
	}
	wslay_frame_context_free(ctx->frame_ctx);
	wslay_event_omsg_free(ctx->omsg);
	free(ctx);
}

static void wslay_event_call_on_frame_recv_start_callback(wslay_event_context_ptr ctx, const struct wslay_frame_iocb *iocb)
{
	if (ctx->callbacks.on_frame_recv_start_callback) {
		struct wslay_event_on_frame_recv_start_arg arg;
		arg.fin = iocb->fin;
		arg.rsv = iocb->rsv;
		arg.opcode = iocb->opcode;
		arg.payload_length = iocb->payload_length;
		ctx->callbacks.on_frame_recv_start_callback(ctx, &arg, ctx->user_data);
	}
}

static void wslay_event_call_on_frame_recv_chunk_callback(wslay_event_context_ptr ctx, const struct wslay_frame_iocb *iocb)
{
	if (ctx->callbacks.on_frame_recv_chunk_callback) {
		struct wslay_event_on_frame_recv_chunk_arg arg;
		arg.data = iocb->data;
		arg.data_length = iocb->data_length;
		ctx->callbacks.on_frame_recv_chunk_callback(ctx, &arg, ctx->user_data);
	}
}

static void wslay_event_call_on_frame_recv_end_callback(wslay_event_context_ptr ctx)
{
	if (ctx->callbacks.on_frame_recv_end_callback) {
		ctx->callbacks.on_frame_recv_end_callback(ctx, ctx->user_data);
	}
}

static int wslay_event_is_valid_status_code(uint16_t status_code)
{
	return (1000 <= status_code && status_code <= 1011 && status_code != 1004 && status_code != 1005 && status_code != 1006) || (3000 <= status_code && status_code <= 4999);
}

static int wslay_event_config_get_no_buffering(wslay_event_context_ptr ctx)
{
	return (ctx->config & WSLAY_CONFIG_NO_BUFFERING) > 0;
}

int wslay_event_recv(wslay_event_context_ptr ctx)
{
	struct wslay_frame_iocb iocb;
	ssize_t r;

	while (ctx->read_enabled) {
		memset(&iocb, 0, sizeof(iocb));
		r = wslay_frame_recv(ctx->frame_ctx, &iocb);
		if (r >= 0) {
			int new_frame = 0;
			/* RSV1 is not allowed on control and continuation frames */
			if ((!wslay_event_verify_rsv_bits(ctx, iocb.rsv)) || (wslay_get_rsv1(iocb.rsv) && (wslay_is_ctrl_frame(iocb.opcode) || iocb.opcode == WSLAY_CONTINUATION_FRAME)) || (ctx->server && !iocb.mask) || (!ctx->server && iocb.mask)) {
				if ((r = wslay_event_queue_close_wrapper(ctx, WSLAY_CODE_PROTOCOL_ERROR, NULL, 0)) != 0) {
					return r;
				}
				break;
			}
			if (ctx->imsg->opcode == 0xffu) {
				if (iocb.opcode == WSLAY_TEXT_FRAME || iocb.opcode == WSLAY_BINARY_FRAME || iocb.opcode == WSLAY_CONNECTION_CLOSE || iocb.opcode == WSLAY_PING || iocb.opcode == WSLAY_PONG) {
					wslay_event_imsg_set(ctx->imsg, iocb.fin, iocb.rsv, iocb.opcode);
					new_frame = 1;
				} else {
					if ((r = wslay_event_queue_close_wrapper(ctx, WSLAY_CODE_PROTOCOL_ERROR, NULL, 0)) != 0) {
						return r;
					}
					break;
				}
			} else if (ctx->ipayloadlen == 0 && ctx->ipayloadoff == 0) {
				if (iocb.opcode == WSLAY_CONTINUATION_FRAME) {
					ctx->imsg->fin = iocb.fin;
				} else if (iocb.opcode == WSLAY_CONNECTION_CLOSE || iocb.opcode == WSLAY_PING || iocb.opcode == WSLAY_PONG) {
					ctx->imsg = &ctx->imsgs[1];
					wslay_event_imsg_set(ctx->imsg, iocb.fin, iocb.rsv, iocb.opcode);
				} else {
					if ((r = wslay_event_queue_close_wrapper(ctx, WSLAY_CODE_PROTOCOL_ERROR, NULL, 0)) != 0) {
						return r;
					}
					break;
				}
				new_frame = 1;
			}
			if (new_frame) {
				if (ctx->imsg->msg_length + iocb.payload_length > ctx->max_recv_msg_length) {
					if ((r = wslay_event_queue_close_wrapper(ctx, WSLAY_CODE_MESSAGE_TOO_BIG, NULL, 0)) != 0) {
						return r;
					}
					break;
				}
				ctx->ipayloadlen = iocb.payload_length;
				wslay_event_call_on_frame_recv_start_callback(ctx, &iocb);
				if (!wslay_event_config_get_no_buffering(ctx) || wslay_is_ctrl_frame(iocb.opcode)) {
					if ((r = wslay_event_imsg_append_chunk(ctx->imsg, iocb.payload_length)) != 0) {
						ctx->read_enabled = 0;
						return r;
					}
				}
			}
			/* If RSV1 bit is set then it is too early for utf-8 validation */
			if ((!wslay_get_rsv1(iocb.rsv) && ctx->imsg->opcode == WSLAY_TEXT_FRAME)
				|| ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE) {
				size_t i;
				if (ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE) {
					i = 2;
				} else {
					i = 0;
				}
				for (; i < iocb.data_length; ++i) {
					uint32_t codep;
					if (decode(&ctx->imsg->utf8state, &codep, iocb.data[i]) == UTF8_REJECT) {
						if ((r = wslay_event_queue_close_wrapper(ctx, WSLAY_CODE_INVALID_FRAME_PAYLOAD_DATA, NULL, 0)) != 0) {
							return r;
						}
						break;
					}
				}
			}
			if (ctx->imsg->utf8state == UTF8_REJECT) {
				break;
			}
			wslay_event_call_on_frame_recv_chunk_callback(ctx, &iocb);
			if (iocb.data_length > 0) {
				if (!wslay_event_config_get_no_buffering(ctx) || wslay_is_ctrl_frame(iocb.opcode)) {
					struct wslay_event_byte_chunk *chunk;
					chunk = wslay_queue_tail(ctx->imsg->chunks);
					wslay_event_byte_chunk_copy(chunk, ctx->ipayloadoff, iocb.data, iocb.data_length);
				}
				ctx->ipayloadoff += iocb.data_length;
			}
			if (ctx->ipayloadoff == ctx->ipayloadlen) {
				if (ctx->imsg->fin && (ctx->imsg->opcode == WSLAY_TEXT_FRAME || ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE) && ctx->imsg->utf8state != UTF8_ACCEPT) {
					if ((r = wslay_event_queue_close_wrapper(ctx, WSLAY_CODE_INVALID_FRAME_PAYLOAD_DATA, NULL, 0)) != 0) {
						return r;
					}
					break;
				}
				wslay_event_call_on_frame_recv_end_callback(ctx);
				if (ctx->imsg->fin) {
					if (ctx->callbacks.on_msg_recv_callback || ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE || ctx->imsg->opcode == WSLAY_PING || ctx->imsg->opcode == WSLAY_PONG) {
						struct wslay_event_on_msg_recv_arg arg;
						uint16_t status_code = 0;
						uint8_t *msg = NULL;
						size_t msg_length = 0;
						if (!wslay_event_config_get_no_buffering(ctx) || wslay_is_ctrl_frame(iocb.opcode)) {
							msg = wslay_event_flatten_queue(ctx->imsg->chunks, ctx->imsg->msg_length);
							if (ctx->imsg->msg_length && !msg) {
								ctx->read_enabled = 0;
								return WSLAY_ERR_NOMEM;
							}
							msg_length = ctx->imsg->msg_length;
						}
						if (ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE) {
							const uint8_t *reason;
							size_t reason_length;
							if (ctx->imsg->msg_length >= 2) {
								memcpy(&status_code, msg, 2);
								status_code = ntohs(status_code);
								if (!wslay_event_is_valid_status_code(status_code)) {
									free(msg);
									if ((r = wslay_event_queue_close_wrapper(ctx, WSLAY_CODE_PROTOCOL_ERROR, NULL, 0)) != 0) {
										return r;
									}
									break;
								}
								reason = msg + 2;
								reason_length = ctx->imsg->msg_length - 2;
							} else {
								reason = NULL;
								reason_length = 0;
							}
							ctx->close_status |= WSLAY_CLOSE_RECEIVED;
							ctx->status_code_recv = status_code == 0 ? WSLAY_CODE_NO_STATUS_RCVD : status_code;
							if ((r = wslay_event_queue_close_wrapper(ctx, status_code, reason, reason_length)) != 0) {
								free(msg);
								return r;
							}
						} else if (ctx->imsg->opcode == WSLAY_PING) {
							struct wslay_event_msg pong_arg;
							pong_arg.opcode = WSLAY_PONG;
							pong_arg.msg = msg;
							pong_arg.msg_length = ctx->imsg->msg_length;
							if ((r = wslay_event_queue_msg(ctx, &pong_arg)) && r != WSLAY_ERR_NO_MORE_MSG) {
								ctx->read_enabled = 0;
								free(msg);
								return r;
							}
						} else if (ctx->imsg->opcode == WSLAY_PONG) {
							struct websocket_info_t *info = ctx->user_data;
							info->data->ping_cnt = 0;
						}
						if (ctx->callbacks.on_msg_recv_callback) {
							arg.rsv = ctx->imsg->rsv;
							arg.opcode = ctx->imsg->opcode;
							arg.msg = msg;
							arg.msg_length = msg_length;
							arg.status_code = status_code;
							ctx->error = 0;
							ctx->callbacks.on_msg_recv_callback(ctx, &arg, ctx->user_data);
							if (ctx->imsg->opcode != WSLAY_CONNECTION_CLOSE) {
								free(msg);
								wslay_event_imsg_reset(ctx->imsg);
								if (ctx->imsg == &ctx->imsgs[1]) {
									ctx->imsg = &ctx->imsgs[0];
								}
								ctx->ipayloadlen = ctx->ipayloadoff = 0;
								break;
							}
						}
						if (ctx->imsg->opcode == WSLAY_CONNECTION_CLOSE) {
							struct websocket_info_t *info = ctx->user_data;
							if (!wslay_queue_empty(ctx->send_ctrl_queue)) {
								wslay_event_send(ctx);
							}
							websocket_update_state(info->data, WEBSOCKET_STOP);
						}
						free(msg);
					}
					wslay_event_imsg_reset(ctx->imsg);
					if (ctx->imsg == &ctx->imsgs[1]) {
						ctx->imsg = &ctx->imsgs[0];
					}
				}
				ctx->ipayloadlen = ctx->ipayloadoff = 0;
			}
		} else {
			if (r != WSLAY_ERR_WANT_READ || (ctx->error != WSLAY_ERR_WOULDBLOCK && ctx->error != 0)) {
				if ((r = wslay_event_queue_close_wrapper(ctx, 0, NULL, 0)) != 0) {
					return r;
				}
				return WSLAY_ERR_CALLBACK_FAILURE;
			}
			break;
		}
	}
	return 0;
}

static void wslay_event_on_non_fragmented_msg_popped(wslay_event_context_ptr ctx)
{
	ctx->omsg->fin = 1;
	ctx->opayloadlen = ctx->omsg->data_length;
	ctx->opayloadoff = 0;
}

static struct wslay_event_omsg *wslay_event_send_ctrl_queue_pop(wslay_event_context_ptr ctx)
{
	/*
	 * If Close control frame is queued, we don't send any control frame
	 * other than Close.
	 */
	if (ctx->close_status & WSLAY_CLOSE_QUEUED) {
		while (!wslay_queue_empty(ctx->send_ctrl_queue)) {
			struct wslay_event_omsg *msg = wslay_queue_top(ctx->send_ctrl_queue);
			wslay_queue_pop(ctx->send_ctrl_queue);
			if (msg->opcode == WSLAY_CONNECTION_CLOSE) {
				return msg;
			} else {
				wslay_event_omsg_free(msg);
			}
		}
		return NULL;
	} else {
		struct wslay_event_omsg *msg = wslay_queue_top(ctx->send_ctrl_queue);
		wslay_queue_pop(ctx->send_ctrl_queue);
		return msg;
	}
}

int wslay_event_send(wslay_event_context_ptr ctx)
{
	struct wslay_frame_iocb iocb;
	ssize_t r;

	while (ctx->write_enabled && (!wslay_queue_empty(ctx->send_queue) || !wslay_queue_empty(ctx->send_ctrl_queue) || ctx->omsg)) {
		if (!ctx->omsg) {
			if (wslay_queue_empty(ctx->send_ctrl_queue)) {
				ctx->omsg = wslay_queue_top(ctx->send_queue);
				wslay_queue_pop(ctx->send_queue);
			} else {
				ctx->omsg = wslay_event_send_ctrl_queue_pop(ctx);
				if (ctx->omsg == NULL) {
					break;
				}
			}
			if (ctx->omsg->type == WSLAY_NON_FRAGMENTED) {
				wslay_event_on_non_fragmented_msg_popped(ctx);
			}
		} else if (!wslay_is_ctrl_frame(ctx->omsg->opcode) && ctx->frame_ctx->ostate == PREP_HEADER && !wslay_queue_empty(ctx->send_ctrl_queue)) {
			if ((r = wslay_queue_push_front(ctx->send_queue, ctx->omsg)) != 0) {
				ctx->write_enabled = 0;
				return r;
			}
			ctx->omsg = wslay_event_send_ctrl_queue_pop(ctx);
			if (ctx->omsg == NULL) {
				break;
			}
			/* ctrl message has WSLAY_NON_FRAGMENTED */
			wslay_event_on_non_fragmented_msg_popped(ctx);
		}
		if (ctx->omsg->type == WSLAY_NON_FRAGMENTED) {
			memset(&iocb, 0, sizeof(iocb));
			iocb.fin = 1;
			iocb.opcode = ctx->omsg->opcode;
			iocb.rsv = ctx->omsg->rsv;
			iocb.mask = ctx->server ^ 1;
			iocb.data = ctx->omsg->data + ctx->opayloadoff;
			iocb.data_length = ctx->opayloadlen - ctx->opayloadoff;
			iocb.payload_length = ctx->opayloadlen;
			r = wslay_frame_send(ctx->frame_ctx, &iocb);
			if (r >= 0) {
				ctx->opayloadoff += r;
				if (ctx->opayloadoff == ctx->opayloadlen) {
					--ctx->queued_msg_count;
					ctx->queued_msg_length -= ctx->omsg->data_length;
					if (ctx->omsg->opcode == WSLAY_CONNECTION_CLOSE) {
						uint16_t status_code = 0;
						ctx->write_enabled = 0;
						ctx->close_status |= WSLAY_CLOSE_SENT;
						if (ctx->omsg->data_length >= 2) {
							memcpy(&status_code, ctx->omsg->data, 2);
							status_code = ntohs(status_code);
						}
						ctx->status_code_sent = status_code == 0 ? WSLAY_CODE_NO_STATUS_RCVD : status_code;
					}
					wslay_event_omsg_free(ctx->omsg);
					ctx->omsg = NULL;
				} else {
					break;
				}
			} else {
				if (r != WSLAY_ERR_WANT_WRITE || (ctx->error != WSLAY_ERR_WOULDBLOCK && ctx->error != 0)) {
					ctx->write_enabled = 0;
					return WSLAY_ERR_CALLBACK_FAILURE;
				}
				break;
			}
		} else {
			if (ctx->omsg->fin == 0 && ctx->obuflimit == ctx->obufmark) {
				int eof = 0;
				r = ctx->omsg->read_callback(ctx, ctx->obuf, sizeof(ctx->obuf), &ctx->omsg->source, &eof, ctx->user_data);
				if (r == 0) {
					break;
				} else if (r < 0) {
					ctx->write_enabled = 0;
					return WSLAY_ERR_CALLBACK_FAILURE;
				}
				ctx->obuflimit = ctx->obuf + r;
				if (eof) {
					ctx->omsg->fin = 1;
				}
				ctx->opayloadlen = (uint64_t)r;
				ctx->opayloadoff = 0;
			}
			memset(&iocb, 0, sizeof(iocb));
			iocb.fin = ctx->omsg->fin;
			iocb.opcode = ctx->omsg->opcode;
			iocb.rsv = ctx->omsg->rsv;
			iocb.mask = ctx->server ? 0 : 1;
			iocb.data = ctx->obufmark;
			iocb.data_length = ctx->obuflimit - ctx->obufmark;
			iocb.payload_length = ctx->opayloadlen;
			r = wslay_frame_send(ctx->frame_ctx, &iocb);
			if (r >= 0) {
				ctx->obufmark += r;
				if (ctx->obufmark == ctx->obuflimit) {
					ctx->obufmark = ctx->obuflimit = ctx->obuf;
					if (ctx->omsg->fin) {
						--ctx->queued_msg_count;
						wslay_event_omsg_free(ctx->omsg);
						ctx->omsg = NULL;
						break;
					} else {
						ctx->omsg->opcode = WSLAY_CONTINUATION_FRAME;
						/* RSV1 is not set on continuation frames */
						ctx->omsg->rsv = ctx->omsg->rsv & ~WSLAY_RSV1_BIT;
					}
				} else {
					break;
				}
			} else {
				if (r != WSLAY_ERR_WANT_WRITE || (ctx->error != WSLAY_ERR_WOULDBLOCK && ctx->error != 0)) {
					ctx->write_enabled = 0;
					return WSLAY_ERR_CALLBACK_FAILURE;
				}
				break;
			}
		}
	}
	return 0;
}

void wslay_event_set_error(wslay_event_context_ptr ctx, int val)
{
	ctx->error = val;
}

int wslay_event_want_read(wslay_event_context_ptr ctx)
{
	return ctx->read_enabled;
}

int wslay_event_want_write(wslay_event_context_ptr ctx)
{
	return ctx->write_enabled && (!wslay_queue_empty(ctx->send_queue) || !wslay_queue_empty(ctx->send_ctrl_queue) || ctx->omsg);
}

void wslay_event_shutdown_read(wslay_event_context_ptr ctx)
{
	ctx->read_enabled = 0;
}

void wslay_event_shutdown_write(wslay_event_context_ptr ctx)
{
	ctx->write_enabled = 0;
}

int wslay_event_get_read_enabled(wslay_event_context_ptr ctx)
{
	return ctx->read_enabled;
}

int wslay_event_get_write_enabled(wslay_event_context_ptr ctx)
{
	return ctx->write_enabled;
}

int wslay_event_get_close_received(wslay_event_context_ptr ctx)
{
	return (ctx->close_status & WSLAY_CLOSE_RECEIVED) > 0;
}

int wslay_event_get_close_sent(wslay_event_context_ptr ctx)
{
	return (ctx->close_status & WSLAY_CLOSE_SENT) > 0;
}

void wslay_event_config_set_allowed_rsv_bits(wslay_event_context_ptr ctx, uint8_t rsv)
{
	/* We currently only allow WSLAY_RSV1_BIT or WSLAY_RSV_NONE */
	ctx->allowed_rsv_bits = rsv & WSLAY_RSV1_BIT;
}

void wslay_event_config_set_no_buffering(wslay_event_context_ptr ctx, int val)
{
	if (val) {
		ctx->config |= WSLAY_CONFIG_NO_BUFFERING;
	} else {
		ctx->config &= ~WSLAY_CONFIG_NO_BUFFERING;
	}
}

void wslay_event_config_set_max_recv_msg_length(wslay_event_context_ptr ctx, uint64_t val)
{
	ctx->max_recv_msg_length = val;
}

uint16_t wslay_event_get_status_code_received(wslay_event_context_ptr ctx)
{
	return ctx->status_code_recv;
}

uint16_t wslay_event_get_status_code_sent(wslay_event_context_ptr ctx)
{
	return ctx->status_code_sent;
}

size_t wslay_event_get_queued_msg_count(wslay_event_context_ptr ctx)
{
	return ctx->queued_msg_count;
}

size_t wslay_event_get_queued_msg_length(wslay_event_context_ptr ctx)
{
	return ctx->queued_msg_length;
}
